package gitbucket.core.controller.api
import gitbucket.core.api.{ApiCommit, ApiContents, ApiError, CreateAFile, JsonFormat}
import gitbucket.core.controller.ControllerBase
import gitbucket.core.service.{RepositoryCommitFileService, RepositoryService}
import gitbucket.core.util.Directory.getRepositoryDir
import gitbucket.core.util.JGitUtil.{CommitInfo, FileInfo, getContentFromId, getFileList}
import gitbucket.core.util._
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
import gitbucket.core.util.Implicits._
import org.eclipse.jgit.api.Git

import scala.util.Using

trait ApiRepositoryContentsControllerBase extends ControllerBase {
    self: ReferrerAuthenticator with WritableUsersAuthenticator with RepositoryCommitFileService =>

    /**
   * i. Get a repository README
   * https://docs.github.com/en/rest/reference/repos#get-a-repository-readme
   */
    get("/api/v3/repos/:owner/:repository/readme")(referrersOnly { repository =>
        Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
            val refStr = params.getOrElse("ref", repository.repository.defaultBranch)
            val files =
                getFileList(git, refStr, ".", maxFiles = context.settings.repositoryViewer.maxFiles)
            files // files should be sorted alphabetically.
                .find { file =>
                    !file.isDirectory &&
                    RepositoryService.readmeFiles.contains(file.name.toLowerCase)
                } match {
                case Some(x) => getContents(
                      repository = repository,
                      path = x.name,
                      refStr = refStr,
                      ignoreCase = true
                    )
                case _ => NotFound()
            }
        }
    })

    /**
   * ii. Get contents
   * https://docs.github.com/en/rest/reference/repos#get-repository-content
   */
    get("/api/v3/repos/:owner/:repository/contents")(referrersOnly { repository =>
        getContents(repository, ".", params.getOrElse("ref", repository.repository.defaultBranch))
    })

    /**
   * ii. Get contents
   * https://docs.github.com/en/rest/reference/repos#get-repository-content
   */
    get("/api/v3/repos/:owner/:repository/contents/*")(referrersOnly { repository =>
        getContents(
          repository,
          multiParams("splat").head,
          params.getOrElse("ref", repository.repository.defaultBranch)
        )
    })

    private def getFileInfo(
        git: Git,
        revision: String,
        pathStr: String,
        ignoreCase: Boolean
    ): Option[FileInfo] = {
        val (dirName, fileName) = pathStr.lastIndexOf('/') match {
            case -1 => (".", pathStr)
            case n  => (pathStr.take(n), pathStr.drop(n + 1))
        }
        if (ignoreCase) {
            getFileList(
              git,
              revision,
              dirName,
              maxFiles = context.settings.repositoryViewer.maxFiles
            ).find(_.name.toLowerCase.equals(fileName.toLowerCase))
        } else {
            getFileList(
              git,
              revision,
              dirName,
              maxFiles = context.settings.repositoryViewer.maxFiles
            ).find(_.name.equals(fileName))
        }
    }

    private def getContents(
        repository: RepositoryService.RepositoryInfo,
        path: String,
        refStr: String,
        ignoreCase: Boolean = false
    ) = {
        Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
            val fileList = getFileList(
              git,
              refStr,
              path,
              maxFiles = context.settings.repositoryViewer.maxFiles
            )
            if (fileList.isEmpty) { // file or NotFound
                getFileInfo(git, refStr, path, ignoreCase).flatMap { f =>
                    val largeFile = params.get("large_file").exists(s => s.equals("true"))
                    val content = getContentFromId(git, f.id, largeFile)
                    request.getHeader("Accept") match {
                        case "application/vnd.github.v3.raw" => {
                            contentType = "application/vnd.github.v3.raw"
                            content
                        }
                        case "application/vnd.github.v3.html" if isRenderable(f.name) => {
                            contentType = "application/vnd.github.v3.html"
                            content.map { c =>
                                List(
                                  "<div data-path=\"",
                                  path,
                                  "\" id=\"file\">",
                                  "<article>",
                                  renderMarkup(
                                    path.split("/").toList,
                                    new String(c),
                                    refStr,
                                    repository,
                                    false,
                                    false,
                                    true
                                  ).body,
                                  "</article>",
                                  "</div>"
                                ).mkString
                            }
                        }
                        case "application/vnd.github.v3.html" => {
                            contentType = "application/vnd.github.v3.html"
                            content.map { c =>
                                List(
                                  "<div data-path=\"",
                                  path,
                                  "\" id=\"file\">",
                                  "<div class=\"plain\">",
                                  "<pre>",
                                  play.twirl.api.HtmlFormat.escape(new String(c)).body,
                                  "</pre>",
                                  "</div>",
                                  "</div>"
                                ).mkString
                            }
                        }
                        case _ =>
                            Some(JsonFormat(ApiContents(f, RepositoryName(repository), content)))
                    }
                }.getOrElse(NotFound())

            } else { // directory
                JsonFormat(fileList.map { f => ApiContents(f, RepositoryName(repository), None) })
            }
        }
    }

    /**
   * iii. Create a file or iv. Update a file
   * https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents
   * if sha is presented, update a file else create a file.
   * requested #2112
   */
    put("/api/v3/repos/:owner/:repository/contents/*")(writableUsersOnly { repository =>
        context.withLoginAccount { loginAccount =>
            JsonFormat(
              for {
                  data <- extractFromJsonBody[CreateAFile]
              } yield {
                  val branch = data.branch.getOrElse(repository.repository.defaultBranch)
                  val commit = Using
                      .resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
                          git =>
                              val revCommit = JGitUtil
                                  .getRevCommitFromId(git, git.getRepository.resolve(branch))
                              revCommit.name
                      }
                  val paths = multiParams("splat").head.split("/")
                  val path = paths.take(paths.size - 1).toList.mkString("/")
                  Using.resource(
                    Git.open(getRepositoryDir(params("owner"), params("repository")))
                  ) { git =>
                      val fileInfo = getFileInfo(git, commit, path, false)

                      fileInfo match {
                          case Some(f) if !data.sha.contains(f.id.getName) =>
                              ApiError(
                                "The blob SHA is not matched.",
                                Some(
                                  "https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents"
                                )
                              )
                          case _ => commitFile(
                                repository,
                                branch,
                                path,
                                Some(paths.last),
                                data.sha.map(_ => paths.last),
                                StringUtil.base64Decode(data.content),
                                data.message,
                                commit,
                                loginAccount,
                                data.committer.map(_.name).getOrElse(loginAccount.fullName),
                                data.committer.map(_.email).getOrElse(loginAccount.mailAddress),
                                context.settings
                              ) match {
                                  case Left(error) =>
                                      ApiError(s"Failed to commit a file: ${error}", None)
                                  case Right((_, None)) =>
                                      ApiError("Failed to commit a file.", None)
                                  case Right((commitId, Some(blobId))) => Map(
                                        "content" -> ApiContents(
                                          "file",
                                          paths.last,
                                          path,
                                          blobId.name,
                                          Some(data.content),
                                          Some("base64")
                                        )(RepositoryName(repository)),
                                        "commit" -> ApiCommit(
                                          git,
                                          RepositoryName(repository),
                                          new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
                                        )
                                      )
                              }
                      }
                  }
              }
            )
        }
    })

    /*
     * v. Delete a file
     * https://docs.github.com/en/rest/reference/repos#delete-a-file
     * should be implemented
     */

    /*
     * vi. Download a repository archive (tar/zip)
     * https://docs.github.com/en/rest/reference/repos#download-a-repository-archive-tar
     * https://docs.github.com/en/rest/reference/repos#download-a-repository-archive-zip
     */

}
